/*********************************************
 * vim:sw=8:ts=8:si:et
 * To use the above modeline in vim you must have "set modeline" in your .vimrc
 * Author: Guido Socher
 * Copyright: GPL V2
 * See http://www.gnu.org/licenses/gpl.html
 *
 * Ethernet remote device and sensor
 * HTTP interface 
 *
 * Chip type           : Atmega88 or Atmega168 or Atmega328 with ENC28J60
 * Note: there is a version number in the text. Search for tuxgraphics
 *
 * Modify: Atommann
 * Date: 2009-07-19
 * Version: 1.0
 *********************************************/
#include <avr/io.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include "ip_arp_udp_tcp.h"
#include "enc28j60.h"
#include "timeout.h"
#include "analog.h"
#include "net.h"
#include "websrv_help_functions.h"

// please modify the following lines. mac and ip have to be unique
// in your local area network. You can not have the same numbers in
// two devices. The IP address may be changed at run-time but the
// MAC address is hardcoded:
static uint8_t mymac[6] = {0x54,0x55,0x58,0x10,0x00,0x29};
// how did I get the mac addr? Translate the first 3 numbers into ascii is: TUX
//
// The IP of this device (can also be changed at run-time, see README file):
//static uint8_t myip[4] = {10,0,0,29};
static uint8_t myip[4] = {192,168,1,9};
// listen port for tcp/www:
#define MYWWWPORT 123
// set this to 1 if you want a login page, otherwise to 0
#define LOGINPAGE 1
// the password string (only characters: a-z,0-9,_,.,-,# ):
static char password[]="secret";
// -------------- do not change anything below this line ----------
// The buffer is the packet size we can handle and its upper limit is
// given by the amount of RAM that the atmega chip has.
#define BUFFER_SIZE 650
static uint8_t buf[BUFFER_SIZE+1];
static volatile uint8_t stepcounter=0;

static uint8_t modifyip=0;  // 2 means set it back to default, 1 means change
// global string buffer
#define STR_BUFFER_SIZE 24
static char strbuf[STR_BUFFER_SIZE+1];
//
//
void store_in_eeprom(void)
{
        eeprom_write_byte((uint8_t *)0x0,19); // magic number
        eeprom_write_block((uint8_t *)myip,(void *)1,sizeof(myip));
}

// return 2 in case of error:
int8_t store_new_ip(char *str)
{
        if (find_key_val(str,strbuf,STR_BUFFER_SIZE,"nip")){
                urldecode(strbuf);
                if (parse_ip(myip,strbuf)==0){
                        store_in_eeprom();
                        return(1);
                }
        }
        return(2);
}

// return values: 1 valid password
//                0 invalid password
uint8_t verify_password(char *str)
{
        // the first characters of the received string are
        // a simple password/cookie:
        if (strncmp(password,str,strlen(password))==0){
                return(1);
        }
        return(0);
}

// takes a string of the form confirm?pw=xxx&rst=1 and analyse it
// return values: -1 not the reset page
//                -2 no rst command
//                -3 valid password
//                 1 rst=1 and passpword OK
/*
int8_t analyse_get_url(char *str)
{
        if (strncmp("confirm",str,7)!=0){
                return(-1);
        }
        if (find_key_val(str,"rst")){
                if (find_key_val(str,"pw")){
                        urldecode(strbuf);
                        if (verify_password(strbuf)==0){
                                return(-3);
                        }else{
                                return(1);
                        }
                }else{
                        return(-3);
                }
        }
        // no rst
        return(-2);
}
*/


// takes a string of the form command/Number and analyse it (e.g "?sw=pd7&a=1 HTTP/1.1")
// The first char of the url ('/') is already removed.
// return values: 0 invalid url data 
//                1 ok
//                2 IP wrong data format
//                3 redirect to new url with pw
//                4 show login page
//                5 reboot button
//                -1 auth error
int8_t analyse_get_url(char *str)
{
        uint8_t on=0;
        uint8_t port=0;
        if (modifyip ==1 && *str == 'i'){
                return(store_new_ip(str));
        }
        // --------
        if (LOGINPAGE==1){
                if (find_key_val(str,strbuf,STR_BUFFER_SIZE,"pw")){
                        urldecode(strbuf);
                        if (verify_password(strbuf)){
                                return(3);
                        }
                }
                if (*str == ' '){
                        return(4);
                }
                // the password must be in the path but url encoded:
                urldecode(str);
                if (verify_password(str)){
                        // move to the end of this portion, max 16 char
                        while(on<16){
                                on++;
                                str++;
                                if (*str=='/'||*str=='?'){
                                        on=0;
                                        break;
                                }
                        }
                        if (on){
                                return(-1);
                        }
                }else{
                        return(-1);
                }
        }else{
                // end of url or '?'
                if (*str != ' ' && *str != '?'){
                        return(0);
                }
        }
        //
        if (find_key_val(str,strbuf,STR_BUFFER_SIZE,"sw")){
                if (strbuf[0] != 'p'){
                        return(0);
                }
                if (strbuf[1] != 'd'){
                        return(0);
                }
                if (strbuf[2] < 0x3a && strbuf[2] > 0x2f){
                        // is a ASCII number, return it
                        port=strbuf[2]-0x30;
                }else{
                        return(0);
                }
                if (find_key_val(str,strbuf,STR_BUFFER_SIZE,"a")){
                        if (strbuf[0] == '1'){
                                on=1;
                        }
                }
        }
        // PD7 controls reed relay
        if (port == 7){
                if (on){
                        return(5);
                }//else{
                        //PORTD &= ~(1<<PORTD7);// transistor off
                //}
        }
        if (port == 6){
                if (on){
                        PORTD|= (1<<PORTD6);// transistor on
                }else{
                        PORTD &= ~(1<<PORTD6);// transistor off
                }
        }
        if (port == 5){
                if (on){
                        PORTD|= (1<<PORTD5);// transistor on
                }else{
                        PORTD &= ~(1<<PORTD5);// transistor off
                }
        }
        // PD4 controls the relay
        if (port == 4){
                if (on){
                        PORTD|= (1<<PORTD4);// transistor on
                }else{
                        PORTD &= ~(1<<PORTD4);// transistor off
                }
        }
        return(1);
}

// answer HTTP/1.0 301 Moved Permanently\r\nLocation: password/\r\n\r\n
// to redirect to the url ending in a slash
uint16_t moved_perm(uint8_t *buf)
{
        uint16_t plen;
        plen=fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 301 Moved Permanently\r\nLocation: "));
        urlencode(password,strbuf);
        plen=fill_tcp_data(buf,plen,strbuf);
        plen=fill_tcp_data_p(buf,plen,PSTR("/\r\nContent-Type: text/html\r\nPragma: no-cache\r\n\r\n"));
        plen=fill_tcp_data_p(buf,plen,PSTR("<h1>301 Moved Permanently</h1>\n"));
        return(plen);
}



uint16_t http200ok(void)
{
        return(fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nPragma: no-cache\r\n\r\n")));
}

// modify own IP:
uint16_t print_webpage_config(uint8_t *buf)
{
        uint16_t plen;
        plen=http200ok();
        plen=fill_tcp_data_p(buf,plen,PSTR("<h2>Change web server IP</h2><pre>\n"));
        plen=fill_tcp_data_p(buf,plen,PSTR("<form action=/i method=get>"));
        plen=fill_tcp_data_p(buf,plen,PSTR("new IP: <input type=text size=12 name=nip value="));
        mk_net_str(strbuf,myip,4,'.',10);
        plen=fill_tcp_data(buf,plen,strbuf);
        plen=fill_tcp_data_p(buf,plen,PSTR("><br>\n"));
        plen=fill_tcp_data_p(buf,plen,PSTR("<input type=submit value=\"change\"></form><hr>"));
        return(plen);
}

uint16_t print_webpage_confirm(uint8_t *buf)
{
        uint16_t plen;
        plen=http200ok();
        plen=fill_tcp_data_p(buf,plen,PSTR("<h2>OK IP updated.</h2> Take out th jumper and power cycle.\n"));
        return(plen);
}

// prepare the webpage by writing the data to the tcp send buffer
uint16_t print_webpage_login(uint8_t *buf)
{
        uint16_t plen;
        plen=http200ok();
        plen=fill_tcp_data_p(buf,plen,PSTR("<h2>Login</h2>"));
        plen=fill_tcp_data_p(buf,plen,PSTR("<pre>"));
        plen=fill_tcp_data_p(buf,plen,PSTR("<form action=/ method=get>"));
        plen=fill_tcp_data_p(buf,plen,PSTR("passw: <input type=password size=12 name=pw>\n"));
        plen=fill_tcp_data_p(buf,plen,PSTR("<input type=submit value=\"login\"></form><hr>"));
        return(plen);
}

// prepare the webpage by writing the data to the tcp send buffer
uint16_t print_webpage(uint8_t *buf)
{
        uint16_t plen;
        //char numstr[6];
        plen=http200ok();
        plen=fill_tcp_data_p(buf,plen,PSTR("<h2>DM500 PWR</h2>"));
        //plen=fill_tcp_data_p(buf,plen,PSTR("<pre>"));
        plen=fill_tcp_data_p(buf,plen,PSTR("\nOutput is: "));
        if (PORTD & (1<<PORTD4)){
                plen=fill_tcp_data_p(buf,plen,PSTR("OFF [<a href=?sw=pd4&a=0>Switch on</a>]"));
        }else{
                plen=fill_tcp_data_p(buf,plen,PSTR("<font color=\"#00FF00\">ON</font> [<a href=?sw=pd4&a=1>Switch off</a>]"));
        }
        //plen=fill_tcp_data_p(buf,plen,PSTR("</pre>"));
        //plen=fill_tcp_data_p(buf,plen,PSTR("\nPD5 output: "));
        //if (PORTD & (1<<PORTD5)){
                //plen=fill_tcp_data_p(buf,plen,PSTR("on  [<a href=?sw=pd5&a=0>change</a>]"));
        //}else{
                //plen=fill_tcp_data_p(buf,plen,PSTR("off [<a href=?sw=pd5&a=1>change</a>]"));
        //}
        //plen=fill_tcp_data_p(buf,plen,PSTR("\nPD6 output: "));
        //if (PORTD & (1<<PORTD6)){
                //plen=fill_tcp_data_p(buf,plen,PSTR("on  [<a href=?sw=pd6&a=0>change</a>]"));
        //}else{
                //plen=fill_tcp_data_p(buf,plen,PSTR("off [<a href=?sw=pd6&a=1>change</a>]"));
        //}
        plen=fill_tcp_data_p(buf,plen,PSTR("<h2>Reset/reboot button</h2>"));
        //plen=fill_tcp_data_p(buf,plen,PSTR("<pre>"));
        //plen=fill_tcp_data_p(buf,plen,PSTR("\nPD7 output: "));
        //if (PORTD & (1<<PORTD7)){
                //plen=fill_tcp_data_p(buf,plen,PSTR("on [<a href=?sw=pd7&a=0>reboot</a>]"));
        //}else{
                plen=fill_tcp_data_p(buf,plen,PSTR("[<a href=?sw=pd7&a=1>Reboot</a>]<br><br>"));
        //}
        //plen=fill_tcp_data_p(buf,plen,PSTR("\ndigital input PD0: "));
        //if (bit_is_clear(PIND,PIND0)){
                //plen=fill_tcp_data_p(buf,plen,PSTR("0"));
        //}else{
                //plen=fill_tcp_data_p(buf,plen,PSTR("1"));
        //}
        //plen=fill_tcp_data_p(buf,plen,PSTR("\ndigital input PD1: "));
        //if (bit_is_clear(PIND,PIND1)){
                //plen=fill_tcp_data_p(buf,plen,PSTR("0"));
        //}else{
                //plen=fill_tcp_data_p(buf,plen,PSTR("1"));
        //}
        //plen=fill_tcp_data_p(buf,plen,PSTR("\nanalog input ADC0: "));
        //itoa(convertanalog(0),numstr,10); // convert integer to string
        //plen=fill_tcp_data(buf,plen,numstr);
        //plen=fill_tcp_data_p(buf,plen,PSTR("\nanalog input ADC1: "));
        //itoa(convertanalog(1),numstr,10); // convert integer to string
        //plen=fill_tcp_data(buf,plen,numstr);

        //plen=fill_tcp_data_p(buf,plen,PSTR("\n\n<a href=\"./\">[refresh page]</a>\n</pre><hr>\n"));
        plen=fill_tcp_data_p(buf,plen,PSTR("\n\n<a href=\"./\">[refresh page]</a>\n<hr>\n"));
        plen=fill_tcp_data_p(buf,plen,PSTR("<br><small>version 1.0<br>\n"));
        return(plen);
}

uint16_t print_webpage_confirm_reset(uint8_t *buf)
{
        uint16_t plen;
        plen=fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nPragma: no-cache\r\n\r\n"));
        plen=fill_tcp_data_p(buf,plen,PSTR("<h2>OK</h2><p>Reset/reboot executed. <a href=\"./\">&gt;&gt;continue</a></p>\n"));
        return(plen);
}


// timer interrupt, step counter and clear relay when at 2
ISR(TIMER1_COMPA_vect){
        if (stepcounter>1){
                stepcounter=0;
                PORTD &= ~(1<<PORTD7);// transistor off
        }
        if (stepcounter>0){
                stepcounter++;
        }
}


// Generate an interrup about ever 1/2s form the 12.5MHz system clock
// Since we have that 1024 prescaler we do not really generate a second
// (1.00000256000655361677s) 
void timer_init(void)
{
        /* write high byte first for 16 bit register access: */
        TCNT1H=0;  /* set counter to zero*/
        TCNT1L=0;
        // Mode 4 table 14-4 page 132. CTC mode and top in OCR1A
        // WGM13=0, WGM12=1, WGM11=0, WGM10=0
        TCCR1A=(0<<COM1B1)|(0<<COM1B0)|(0<<WGM11);
        TCCR1B=(1<<CS12)|(1<<CS10)|(1<<WGM12)|(0<<WGM13); // crystal clock/1024

        // divide crystal clock by 16+1:
        //ICR1H=0;
        //ICR1L=16; // top for counter
        // At what value to cause interrupt. You can use this for calibration
        // of the clock. Theoretical value for 25MHz: 6103=0x17 and 0xd7
        OCR1AH=0x17;
        OCR1AL=0xd7;
        // interrupt mask bit:
        TIMSK1 = (1 << OCIE1A);
}

int main(void){

        
        uint16_t plen;
        uint16_t dat_p;
        int8_t cmd;
        
        // set the clock speed to "no pre-scaler" (8MHz with internal osc or 
        // full external speed)
        // set the clock prescaler. First write CLKPCE to enable setting of clock the
        // next four instructions.
        CLKPR=(1<<CLKPCE); // change enable
        CLKPR=0; // "no pre-scaler"
        _delay_loop_1(50); // 12ms

        /* enable PD2/INT0, as input */
        DDRD&= ~(1<<DDD2);

        // test PD3=reset to default 
        DDRD&= ~(1<<PIND3);
        PORTD|=1<<PIND3; // internal pullup resistor on
        // test PD2=change IP
        DDRD&= ~(1<<PIND2);
        PORTD|=1<<PIND2; // internal pullup resistor on

        _delay_loop_1(50); // 12ms
        if (bit_is_clear(PIND,PIND2)){
                modifyip=1;
        }
        if (bit_is_clear(PIND,PIND3)){
                // make eeprom data invalid
                eeprom_write_byte((uint8_t *)0x0,1); // delete magic number
        }
        _delay_loop_1(50); // 12ms
        if (eeprom_read_byte((uint8_t *)0x0) == 19){
                // ok magic number matches accept values
                eeprom_read_block((uint8_t *)myip,(void *)1,sizeof(myip));
        }
        /*initialize enc28j60*/
        enc28j60Init(mymac);
        enc28j60clkout(2); // change clkout from 6.25MHz to 12.5MHz
        _delay_loop_1(50); // 12ms
        //
        adc_ref_pin_init();
        
        // LED
        /* enable PB1, LED as output */
        //DDRB|= (1<<DDB1);
        /* set output to Vcc, LED off */
        //PORTB|= (1<<PORTB1);

        // the transistor on PD7
        DDRD|= (1<<DDD7);
        PORTD &= ~(1<<PORTD7);// transistor off
        // the transistor on PD6
        DDRD|= (1<<DDD6);
        PORTD &= ~(1<<PORTD6);// transistor off
        // the transistor on PD5
        DDRD|= (1<<DDD5);
        PORTD &= ~(1<<PORTD5);// transistor off
        // the transistor on PD4
        DDRD|= (1<<DDD4);
        PORTD &= ~(1<<PORTD4);// transistor off
        // digital input:
        DDRD&= ~(1<<DDD0);
        // pullup on
        PORTD|= (1<<PIND0);
        // digital input:
        DDRD&= ~(1<<DDD1);
        // pullup on
        PORTD|= (1<<PIND1);
        
        /* Magjack leds configuration, see enc28j60 datasheet, page 11 */
        // LEDB=yellow LEDA=green
        //
        // 0x476 is PHLCON LEDA=links status, LEDB=receive/transmit
        // enc28j60PhyWrite(PHLCON,0b0000 0100 0111 01 10);
        enc28j60PhyWrite(PHLCON,0x476);
        _delay_loop_1(50); // 12ms
        

        //init the ethernet/ip layer:
        init_ip_arp_udp_tcp(mymac,myip,MYWWWPORT);
        timer_init();

        sei(); // interrupt enable

        while(1){
                // handle ping and wait for a tcp packet:
                plen=enc28j60PacketReceive(BUFFER_SIZE, buf);
                dat_p=packetloop_icmp_tcp(buf,plen);

                /* dat_p will ne unequal to zero if there is a valid
                 * http get */
                if(dat_p==0){
                        continue;
                }
                if (strncmp("GET ",(char *)&(buf[dat_p]),4)!=0){
                        // head, post and other methods:
                        //
                        // for possible status codes see:
                        // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
                        plen=fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n<h1>200 OK</h1>"));
                        goto SENDTCP;
                }
                if (strncmp("/ ",(char *)&(buf[dat_p+4]),2)==0){
                        if (modifyip){
                                plen=print_webpage_config(buf);
                                goto SENDTCP;
                        }
                }
                // analyse the url and do possible port changes:
                // move one char ahead:
                cmd=analyse_get_url((char *)&(buf[dat_p+5]));
                // for possible status codes see:
                // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
                if (cmd==-1){
                        plen=fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 401 Unauthorized\r\nContent-Type: text/html\r\n\r\n<h1>401 Unauthorized</h1>"));
                        goto SENDTCP;
                }
                if (cmd==0){
                        plen=fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 404 Not Found\r\nContent-Type: text/html\r\n\r\n<h1>404 Not Found</h1>"));
                        goto SENDTCP;
                }
                if (cmd==2){
                        plen=fill_tcp_data_p(buf,0,PSTR("HTTP/1.0 406 Not Acceptable\r\nContent-Type: text/html\r\n\r\n<h1>406 IP addr. format wrong</h1>"));
                        goto SENDTCP;
                }
                if (cmd==4){
                        plen=print_webpage_login(buf);
                        goto SENDTCP;
                }
                if (cmd==3){
                        plen=moved_perm(buf);
                        goto SENDTCP;
                }
                if (cmd==5){
                        PORTD|= (1<<PORTD7);// transistor on
                        stepcounter=1; // trigger counter to clear transistor 1 sec later
                        plen=print_webpage_confirm_reset(buf);
                        goto SENDTCP;
                }
                // just display the status:
                if (modifyip){
                        plen=print_webpage_confirm(buf);
                }else{
                        plen=print_webpage(buf);
                }
                //
SENDTCP:
                www_server_reply(buf,plen); // send web page data
                // tcp port www end
                //
        }
        return (0);
}
